2012-05-02 51 views
8

Tôi là tiện ích mở rộng lập trình cho trò chơi cung cấp API cho (mod) của chúng tôi. API này cung cấp nhiều thứ khác nhau, nhưng nó có một giới hạn. API chỉ dành cho 'động cơ', có nghĩa là tất cả các sửa đổi (mod) đã được phát hành dựa trên động cơ, không cung cấp/có bất kỳ loại API nào (mod cụ thể). Tôi đã tạo ra một 'máy quét chữ ký' (lưu ý: plugin của tôi được tải như một thư viện được chia sẻ, được biên dịch với -share & -fPIC) tìm các chức năng quan tâm (dễ dàng vì tôi đang sử dụng linux). Vì vậy, để giải thích, tôi sẽ có một trường hợp cụ thể: Tôi đã tìm thấy địa chỉ cho một chức năng quan tâm, tiêu đề chức năng của nó là rất đơn giản int * InstallRules(void);. Nó không có gì (void) và trả về một con trỏ nguyên (cho một đối tượng mà tôi quan tâm). Bây giờ, những gì tôi muốn làm, là để tạo ra một đường vòng (và nhớ rằng tôi có địa chỉ bắt đầu của hàm), với chức năng của riêng tôi, mà tôi muốn cư xử như thế này:Lắp ráp đường vòng và lắp ráp GCC (Linux)

void MyInstallRules(void) 
{ 
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function 
     return; 
    int * val = InstallRules(); // <-- Call original function 
    PostHook(val); // <-- Call post hook, if interest of original functions return value 
} 

Bây giờ đây là thỏa thuận; Tôi không có kinh nghiệm gì về chức năng hooking, và tôi chỉ có một kiến ​​thức mỏng về lắp ráp nội tuyến (AT & T). Các gói đường vòng được tạo sẵn trên Internet là chỉ cho các cửa sổ hoặc đang sử dụng một phương pháp hoàn toàn khác (tức là tải trước một dll để ghi đè lên gói gốc). Nên về cơ bản; tôi nên làm gì để đi đúng hướng? Tôi có nên đọc về các quy ước cuộc gọi (cdecl trong trường hợp này) và tìm hiểu về lắp ráp nội tuyến hay phải làm gì? Tốt nhất có lẽ sẽ là một lớp wrapper đã có chức năng cho Linux detouring. Cuối cùng, tôi muốn một cái gì đó đơn giản như này:

void * addressToFunction = SigScanner.FindBySig("Signature_ASfs&43"); // I've already done this part 
void * original = PatchFunc(addressToFunction, addressToNewFunction); // This replaces the original function with a hook to mine, but returns a pointer to the original function (relocated ofcourse) 
// I might wait for my hook to be called or whatever 
// .... 

// And then unpatch the patched function (optional) 
UnpatchFunc(addressToFunction, addressToNewFunction); 

Tôi hiểu rằng tôi sẽ không thể để có được một câu trả lời hoàn toàn đáp ứng ở đây, nhưng tôi hơn sẽ đánh giá cao một số giúp đỡ với các hướng dẫn để mất bởi vì tôi đang ở trên lớp băng mỏng ở đây ... Tôi đã đọc về tẩy rửa nhưng hầu như không có bất kỳ tài liệu nào (đặc biệt cho Linux), và tôi đoán tôi muốn thực hiện cái được gọi là 'tấm bạt lò xo' nhưng tôi không thể để tìm cách thu nhận kiến ​​thức này.

LƯU Ý: Tôi cũng quan tâm đến việc _thiscall, nhưng từ những gì tôi đã đọc mà không phải là quá khó để gọi với GNU gọi ước

+0

Chỉ cần ném nó ra khỏi đó, bạn đã xem giải pháp phi kỹ thuật chưa, tức là * yêu cầu * tác giả cung cấp API? –

+0

@KarlBielefeldt Đó sẽ là khó khăn ... GoldSrc (cho Half-Life) là khoảng 12 tuổi bây giờ tôi nghĩ? –

Trả lời

12

là dự án này để xây dựng một "khuôn khổ" đó (?) sẽ cho phép người khác móc các chức năng khác nhau trong các tệp nhị phân khác nhau? Hay chỉ là bạn cần phải móc chương trình cụ thể này mà bạn có?

Trước tiên, giả sử bạn muốn điều thứ hai, bạn chỉ có một hàm trong một tệp nhị phân mà bạn muốn móc, lập trình và đáng tin cậy. Vấn đề chính khi thực hiện điều này là việc thực hiện điều này một cách đáng tin cậy là một trò chơi rất khó khăn, nhưng nếu bạn sẵn sàng thực hiện một số thỏa hiệp, thì nó chắc chắn có thể thực hiện được. Ngoài ra chúng ta hãy giả định đây là điều x86.

Nếu bạn muốn móc một hàm, có một số tùy chọn cách thực hiện. Điều gì Detoursvá nội tuyến. Họ có một cái nhìn tổng quan tốt đẹp về cách nó hoạt động trong một Research PDF document. Ý tưởng cơ bản là bạn có một hàm, ví dụ:

00E32BCE /$ 8BFF   MOV EDI,EDI 
00E32BD0 |. 55    PUSH EBP 
00E32BD1 |. 8BEC   MOV EBP,ESP 
00E32BD3 |. 83EC 10  SUB ESP,10 
00E32BD6 |. A1 9849E300 MOV EAX,DWORD PTR DS:[E34998] 
... 
... 

Bây giờ bạn thay thế đầu của hàm với một CALL hoặc JMP để chức năng của bạn và lưu các byte gốc mà bạn ghi đè lên với miếng vá ở đâu đó:

00E32BCE /$ E9 XXXXXXXX JMP MyHook 
00E32BD3 |. 83EC 10  SUB ESP,10 
00E32BD6 |. A1 9849E300 MOV EAX,DWORD PTR DS:[E34998] 

(Lưu ý rằng tôi ghi đè lên 5 byte .) Bây giờ chức năng của bạn được gọi với cùng các tham số và quy ước gọi tương tự như hàm ban đầu.Nếu chức năng của bạn muốn gọi bản gốc (nhưng nó không phải), bạn tạo "tấm bạt lò xo", 1) chạy các lệnh gốc đã được ghi đè 2) jmps đến phần còn lại của hàm gốc:

Trampoline: 
    MOV EDI,EDI 
    PUSH EBP 
    MOV EBP,ESP 
    JMP 00E32BD3 

Và đó là nó, bạn chỉ cần xây dựng chức năng trampoline trong thời gian chạy bằng cách phát ra các hướng dẫn của bộ xử lý. Phần khó của quá trình này là làm cho nó hoạt động đáng tin cậy, cho bất kỳ chức năng nào, đối với bất kỳ quy ước gọi điện nào và cho các hệ điều hành/nền tảng khác nhau. Một trong những vấn đề là nếu 5 byte mà bạn muốn ghi đè kết thúc ở giữa một lệnh. Để phát hiện "kết thúc của hướng dẫn", về cơ bản bạn cần phải bao gồm một bộ tách rời, bởi vì có thể có bất kỳ lệnh nào ở đầu hàm. Hoặc khi hàm này ngắn hơn 5 byte (một hàm luôn trả về 0 có thể được viết là XOR EAX,EAX; RETN chỉ là 3 byte).

Hầu hết các trình biên dịch/trình biên dịch hiện tại tạo ra một hàm prolog dài 5 byte, chính xác cho mục đích này, hooking. Xem rằng MOV EDI, EDI? Nếu bạn tự hỏi, "tại sao chúng lại di chuyển edi sang edi? Điều đó không làm gì cả !?" bạn hoàn toàn chính xác, nhưng đây là mục đích của prolog, chính xác là 5 byte (không phải là kết thúc ở giữa lệnh). Lưu ý rằng ví dụ tháo gỡ không phải là thứ tôi tạo ra, nó là calc.exe trên Windows Vista.

Phần còn lại của việc thực hiện móc chỉ là chi tiết kỹ thuật, nhưng chúng có thể mang lại cho bạn nhiều giờ đau, bởi vì đó là phần khó nhất. Ngoài ra hoạt động bạn đã mô tả trong câu hỏi của bạn:

void MyInstallRules(void) 
{ 
    if(PreHook() == block) // <-- First a 'pre' hook which can block the function 
     return; 
    int * val = InstallRules(); // <-- Call original function 
    PostHook(val); // <-- Call post hook, if interest of original functions return value 
} 

dường như còn tồi tệ hơn những gì tôi đã mô tả (và những gì Đường vòng không), ví dụ bạn có thể muốn "không gọi bản gốc", nhưng quay trở lại một số giá trị khác nhau. Hoặc gọi hàm gốc hai lần. Thay vào đó, hãy để trình xử lý hook của bạn quyết định xem nó sẽ gọi hàm ban đầu ở đâu và ở đâu. Ngoài ra, bạn không cần hai hàm xử lý cho một hook.

Nếu bạn không có đủ kiến ​​thức về công nghệ bạn cần cho điều này (chủ yếu là lắp ráp), hoặc không biết cách làm móc, tôi khuyên bạn nên nghiên cứu những gì mà Detours làm. Hook nhị phân của riêng bạn và mất một trình gỡ lỗi (ví dụ OllyDbg) để xem ở cấp độ lắp ráp những gì nó chính xác đã làm, những gì hướng dẫn đã được đặt và ở đâu. Ngoài ra this tutorial có thể có ích.

Dù sao, nếu nhiệm vụ của bạn là móc một số chức năng trong một chương trình cụ thể, thì điều này là có thể thực hiện được và nếu bạn gặp bất kỳ sự cố nào, chỉ cần hỏi lại ở đây. Về cơ bản, bạn có thể làm rất nhiều giả định (như các prologs chức năng hoặc các quy ước được sử dụng) sẽ làm nhiệm vụ của bạn dễ dàng hơn nhiều.

Nếu bạn muốn tạo một số khung gắn kết đáng tin cậy, thì vẫn là một câu chuyện hoàn toàn khác và trước tiên bạn nên bắt đầu bằng cách tạo móc đơn giản cho một số ứng dụng đơn giản. Cũng cần lưu ý rằng kỹ thuật này không phải là hệ điều hành cụ thể, nó giống nhau trên tất cả các nền tảng x86, nó sẽ hoạt động trên cả Linux lẫn Windows. Điều gì là Hệ điều hành cụ thể là bạn có thể sẽ phải thay đổi bảo vệ bộ nhớ của mã ("mở khóa" nó, vì vậy bạn có thể ghi vào nó), được thực hiện với mprotect trên Linux và với VirtualProtect trên Windows. Ngoài ra các quy ước gọi là khác nhau, đó là những gì bạn có thể giải quyết bằng cách sử dụng cú pháp chính xác trong trình biên dịch của bạn.

Một vấn đề khác là "DLL injection" (trên Linux nó có thể sẽ được gọi là "chia sẻ thư viện tiêm" nhưng thuật ngữ DLL injection được biết đến rộng rãi). Bạn cần phải đặt mã của bạn (thực hiện móc) vào chương trình. Đề xuất của tôi là nếu có thể, chỉ cần sử dụng biến môi trường LD_PRELOAD, trong đó bạn có thể chỉ định một thư viện sẽ được tải vào chương trình ngay trước khi nó chạy.Điều này đã được mô tả trong SO nhiều lần, như ở đây: What is the LD_PRELOAD trick?. Nếu bạn phải làm điều này trong thời gian chạy, tôi sợ bạn sẽ cần phải nhận được với gdb hoặc ptrace, mà theo ý kiến ​​của tôi là khá khó khăn (ít nhất là điều ptrace) để làm. Tuy nhiên, bạn có thể đọc ví dụ this article on codeproject hoặc this ptrace tutorial.

Tôi cũng tìm thấy một số tài nguyên đẹp:

Ngoài ra một điểm khác: "Bản vá nội tuyến" này không phải là cách duy nhất để thực hiện việc này. Thậm chí còn có những cách đơn giản hơn, ví dụ: nếu chức năng là virtual hoặc nếu đó là chức năng xuất thư viện, bạn có thể bỏ qua tất cả việc lắp ráp/tháo gỡ/điều JMP và chỉ cần thay thế con trỏ đến hàm đó (trong bảng chức năng ảo hoặc trong bảng biểu tượng đã xuất).

+0

Đây là lý do tại sao tôi yêu StackOverflow. Tôi không thể yêu cầu một câu trả lời tốt hơn! Tôi chắc chắn sẽ đưa ra một bình luận rộng rãi hơn, nhưng tôi muốn đọc lên tất cả các liên kết của bạn trước, nhưng tôi phải hỏi về "DLL injection". Điều đó có liên quan đến tôi không? Kể từ khi plugin của tôi là một dll/như vậy, mà 'mod'/trò chơi tải (động) có nghĩa là tôi sẽ chỉ có thể đặt mã 'hooking' của tôi trong thư viện của tôi thay vì sử dụng LD_PRELOAD? –

+0

Được rồi, sau đó bạn có ít vấn đề hơn. Dù sao, trò chơi đó là gì? – kuba

+0

Tôi đang lập trình cái gọi là 'plugin metamod' cho trò chơi FPS nổi tiếng Counter-Strike (sử dụng một công cụ gọi là GoldSrc, đó là ~ 12 tuổi). Phần bực bội là tôi có gần như mọi thứ tôi cần. Tôi thực sự có mã nguồn của hàm mà tôi đang cố gắng móc, tôi có thể tìm địa chỉ của nó một cách năng động và tôi biết quy ước gọi của nó, vì vậy tôi tuyệt vọng cố gắng giải quyết câu đố cuối cùng này, nhưng tôi không sẵn sàng để dành 2 tháng đào qua các cuốn sách lắp ráp, cái khác ... –