2009-03-09 50 views
6

Để cho phép truy cập vào các API Win32 từ một ngôn ngữ kịch bản (viết bằng C), tôi muốn viết một hàm như sau:Làm thế nào tôi có thể viết một hàm C chung để gọi một hàm Win32?

void Call(LPCSTR DllName, LPCSTR FunctionName, 
    LPSTR ReturnValue, USHORT ArgumentCount, LPSTR Arguments[]) 

mà sẽ gọi, quát, bất kỳ chức năng Win32 API.

(thông số LPSTR về cơ bản được sử dụng làm mảng byte - giả sử rằng chúng đã được định kích thước chính xác để lấy đúng kiểu dữ liệu bên ngoài cho hàm. Ngoài ra tôi tin rằng cần thêm một số phức tạp để phân biệt giữa con trỏ và không đối số con trỏ nhưng tôi bỏ qua điều đó cho mục đích của câu hỏi này).

Sự cố tôi gặp phải là chuyển các đối số vào các hàm API Win32. Bởi vì đây là những stdcall tôi không thể sử dụng varargs để thực hiện 'Call' phải biết về số lượng các đối số trước và do đó nó không thể là chung ...

Tôi nghĩ rằng tôi có thể làm điều này với mã lắp ráp (bởi looping trên các đối số, đẩy mỗi stack) nhưng điều này có thể trong C tinh khiết?

Cập nhật: Tôi đã đánh dấu câu trả lời 'Không có gì là không thể' như được chấp nhận ngay bây giờ. Dĩ nhiên tôi sẽ thay đổi điều này nếu một giải pháp dựa trên C có ánh sáng.

Cập nhật:ruby/dl có vẻ như nó có thể được triển khai bằng cơ chế phù hợp. Mọi chi tiết về điều này sẽ được đánh giá cao.

Trả lời

5

Không, tôi không nghĩ rằng nó có thể làm được mà không cần viết một số hội đồng. Lý do là bạn cần điều khiển chính xác hơn những gì là trên stack trước khi bạn gọi hàm mục tiêu, và không có cách nào thực sự để làm điều đó trong C. tinh khiết Đó là, tất nhiên, đơn giản để làm trong hội mặc dù.

Ngoài ra, bạn đang sử dụng PCSTR cho tất cả các đối số này, thực sự chỉ là const char *. Nhưng vì tất cả các arg này không phải là chuỗi, những gì bạn thực sự muốn sử dụng cho giá trị trả lại và cho Đối số [] là void * hoặc LPVOID. Đây là loại bạn nên sử dụng khi bạn không biết loại thực sự của các đối số, chứ không phải đúc họ char *.

+0

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. –

+0

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

8

Trước tiên: Bạn không thể vượt qua một loại như một tham số trong C. Các lựa chọn duy nhất bạn là trái với macro.

Sơ đồ này hoạt động với một chút sửa đổi (mảng void * cho đối số), miễn là bạn đang thực hiện LoadLibrary/GetProcAddress để gọi hàm Win32. Có một chuỗi tên chức năng khác sẽ không được sử dụng. Trong C, cách duy nhất bạn gọi một chức năng là thông qua tên của nó (một định danh) mà trong nhiều trường hợp phân rã đến một con trỏ đến hàm. Bạn cũng phải chăm sóc đúc giá trị trả lại.

đặt cược tốt nhất của tôi:

// define a function type to be passed on to the next macro 
#define Declare(ret, cc, fn_t, ...) typedef ret (cc *fn_t)(__VA_ARGS__) 

// for the time being doesn't work with UNICODE turned on 
#define Call(dll, fn, fn_t, ...) do {\ 
    HMODULE lib = LoadLibraryA(dll); \ 
    if (lib) { \ 
     fn_t pfn = (fn_t)GetProcAddress(lib, fn); \ 
     if (pfn) { \ 
      (pfn)(__VA_ARGS__); \ 
     } \ 
     FreeLibrary(lib); \ 
    } \ 
    } while(0) 

int main() { 
    Declare(int, __stdcall, MessageBoxProc, HWND, LPCSTR, LPCSTR, UINT); 

    Call("user32.dll", "MessageBoxA", MessageBoxProc, 
      NULL, ((LPCSTR)"?"), ((LPCSTR)"Details"), 
      (MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2)); 

    return 0; 
} 
+0

Vì vậy, bạn đang nói rằng nó * là * có thể làm điều này trong C - vấn đề là cuộc gọi thực tế đến chức năng Win32 (mà rõ ràng là tôi không kiểm soát) thay vì nguyên mẫu của chức năng 'Gọi' trong ví dụ của tôi? –

+0

Không sử dụng __VA_ARGS__ ở đây giả định rằng cuộc gọi là cdecl hơn là stdcall (và do đó các đối số sẽ được đẩy lên ngăn xếp theo thứ tự sai)? –

+0

__VA_ARGS__ là macro Vĩ mô. Điều này chấm dứt tồn tại sau bước xử lý trước. Điều này không giống như va_args cho các hàm variadic. Cũng lưu ý rằng tôi đang chuyển loại hàm như một tham số macro. – dirkgently

0

Có một chức năng như vậy có vẻ như một ý tưởng tồi, nhưng bạn có thể thử điều này:

int Call(LPCSTR DllName, LPCSTR FunctionName, 
    USHORT ArgumentCount, int args[]) 
{ 
    void STDCALL (*foobar)()=lookupDLL(...); 

    switch(ArgumentCount) { 
     /* Note: If these give some compiler errors, you need to cast 
      each one to a func ptr type with suitable number of arguments. */ 
     case 0: return foobar(); 
     case 1: return foobar(args[0]); 
     ... 
    } 
} 

Trên một hệ thống 32-bit, gần như tất cả các giá trị phù hợp vào một từ 32-bit và giá trị ngắn hơn được đẩy vào stack như từ 32-bit cho các đối số chức năng cuộc gọi, vì vậy bạn nên có thể gọi hầu như tất cả các chức năng Win32 API theo cách này, chỉ cần đúc các đối số int và giá trị trả về từ int cho các loại thích hợp.

+0

Để thực hiện chung, tôi không thực sự muốn có mã chuyển đổi (hoặc nếu có) cho các số đối số khác nhau, mặc dù tôi đồng ý rằng có giới hạn thực tế đối với số đối số mà tôi cần phải cung cấp. –

+0

Bạn cũng cần gọi hàm cdecl ... – RBerteig

0

Tôi không chắc chắn nếu nó sẽ được quan tâm đến bạn, nhưng một tùy chọn sẽ được để shell ra để RunDll32.exe và có nó thực hiện các cuộc gọi chức năng cho bạn. RunDll32 có một số hạn chế và tôi không tin rằng bạn có thể truy cập vào giá trị trả về nào, nhưng nếu bạn tạo thành các đối số dòng lệnh đúng, nó sẽ gọi hàm.

Dưới đây là một link

+0

Cảm ơn bạn đã liên kết nhưng nó cho thấy rằng RunDll32 rõ ràng không cho phép gọi bất kỳ hàm Win32 API nào ... –

0

Trước tiên, bạn nên thêm kích thước của mỗi đối số là một tham số phụ. Nếu không, bạn cần phải divine kích thước của mỗi tham số cho mỗi chức năng để đẩy vào ngăn xếp, có thể cho các chức năng WinXX vì chúng phải tương thích với các tham số mà chúng được ghi lại, nhưng tẻ nhạt. Thứ hai, không có một cách "thuần túy C" để gọi một hàm mà không biết các đối số ngoại trừ một hàm varargs, và không có ràng buộc về quy ước gọi được sử dụng bởi một hàm trong một .DLL.

Thực ra, phần thứ hai quan trọng hơn lần đầu tiên. Về lý thuyết, bạn có thể thiết lập cấu trúc macro/# include tiền xử lý để tạo tất cả các kết hợp các kiểu tham số lên đến 11 tham số, nhưng điều đó ngụ ý rằng bạn biết trước thời gian các loại sẽ được chuyển qua chức năng của bạn Call. Đó là loại điên nếu bạn hỏi tôi.

Mặc dù, nếu bạn thực sự muốn thực hiện điều này một cách không an toàn, bạn có thể chuyển tên C++ bị cắt xén và sử dụng UnDecorateSymbolName để trích xuất các loại tham số. Tuy nhiên, điều đó sẽ không hoạt động đối với các hàm được xuất với liên kết C.

1

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.

+0

+1, nhưng ý định của tôi là thêm khả năng tự ý gọi vào API Win32 từ một ngôn ngữ kịch bản được thực hiện trong C (thay vì mở một lỗ hổng bảo mật!). Tôi muốn thực hiện không phụ thuộc vào một trình biên dịch C/linker nếu có thể. –

+0

Cảm ơn bạn đã cập nhật (tiếc là tôi không thể upvote bạn một lần nữa!). Tôi đang tìm kiếm một giải pháp dựa trên C, lý tưởng nhưng tùy chọn .NET là thực phẩm cho sự suy nghĩ. –

+0

Kỹ thuật này có các trường hợp sử dụng không có ý định là lỗ hổng bảo mật. Một là cho phép các tác giả kịch bản điều chỉnh mã nước ngoài hoặc các API hệ thống sang ngôn ngữ kịch bản của chúng mà không cần phải biên dịch. – RBerteig

2

Các bài đăng khác là đúng về nhu cầu gần như nhất định để lắp ráp hoặc các thủ thuật không chuẩn khác để thực hiện cuộc gọi, chưa kể tất cả chi tiết của các quy ước gọi điện thực tế.

Dll Windows sử dụng ít nhất hai quy ước gọi điện riêng biệt cho các chức năng: stdcallcdecl. Bạn sẽ cần phải xử lý cả hai, và thậm chí có thể cần phải tìm ra những gì để sử dụng.

Một cách để giải quyết vấn đề này là sử dụng thư viện hiện có để đóng gói nhiều chi tiết. Thật ngạc nhiên, có một: libffi. Một ví dụ về việc sử dụng nó trong môi trường kịch bản là việc triển khai Lua Alien, một mô-đun Lua cho phép các giao diện cho các DLL tùy ý được tạo ra trong Lua thuần túy ngoài bản thân Alien.

1

Bạn có thể thử một cái gì đó như thế này - nó hoạt động tốt cho các chức năng API win32:

int CallFunction(int functionPtr, int* stack, int size) 
{ 
    if(!stack && size > 0) 
     return 0; 
    for(int i = 0; i < size; i++) { 
     int v = *stack; 
     __asm { 
      push v 
     } 
     stack++; 
    } 
    int r; 
    FARPROC fp = (FARPROC) functionPtr; 
    __asm { 
     call fp 
     mov dword ptr[r], eax 
    } 
    return r; 
} 

Các thông số trong "ngăn xếp" tranh luận nên theo thứ tự ngược (vì đây là thứ tự mà chúng được đẩy lên cây rơm).

Các vấn đề liên quan