2016-07-15 2 views
2

вот мой вопрос: В xxx.lib есть статическая библиотека (xxx.lib) и некоторые файлы C, которые вызывают функцию foo(). Я надеюсь получить уведомление с уведомлением каждый раз, когда вызывается foo(). Но мне не разрешено изменять какой-либо исходный код, написанный другими.Как перехватить вызов статической библиотеки на языке C?

Я провел несколько дней в Интернете и нашел несколько похожих Q & Так как ни одно из этих предложений не могло действительно решить мою проблему. Я перечислю некоторые из них:

  1. использование gcc -wrap: Override a function call in C Слава богу, я использую Microsoft C компилятор и компоновщик, и я не могу найти эквивалентный вариант как -wrap.

  2. Microsoft Detours: Detours перехватывает вызовы C во время выполнения и перенаправляет вызов функции батута. Но Detours доступен только для версии IA32, и он не является открытым исходным кодом.

  3. Я думаю об инъекции инструкции jmp в начале функции foo(), чтобы перенаправить ее на мою собственную функцию. Однако это не представляется возможным, когда foo() пуст, как

    void foo() ---> will be compiled into 0xC3 (ret) 
        {    but it'll need at least 8 bytes to inject a jmp 
        } 
    
  4. Я нашел технологию под названием Hotpatch на MSDN. В нем говорится, что компоновщик добавит серверные байты заполнения в начале каждой функции. Это здорово, потому что я могу заменить прописные байты командой jmp, чтобы реализовать перехват во время выполнения! Но когда я использую параметр/FUNCTIONPADMIN с линкера, это дает мне предупреждение:

    LINK : warning LNK4044: unrecognized option '/FUNCTIONPADMIN'; ignored 
    

    Кто-нибудь может сказать мне, как я мог бы сделать «hotpatchable» изображение правильно? Является ли это приемлемым решением для моего вопроса?

Есть ли у меня еще одна надежда на это?

+0

Я не думаю, что это возможно без большого ворчания. Объектные файлы не предназначены для этого. – fuz

+0

Если честно, я бы пошел на вариант 3. Но сначала вам нужно будет найти функцию, которую вы хотите перенаправить, так как экспорта не будет, например, когда у вас есть DLL. Так что да, это можно сделать, но есть много работы. Ссылка, которая может помочь: [Малый учебник] (https://securitycafe.ro/2015/01/28/intercepting-functions-from-statically-linked-libraries/) – inzanez

+0

Да, я видел эту статью. Но это не практично в реальном проекте. Я надеюсь на метод перехвата вызова функции во время выполнения. Интересно, как это делало Detours .... – Sheldon

ответ

0

Я не думаю, что это можно сделать без изменения кода. Самый простой способ, который я могу придумать, - это написать обертку для вашей функции void foo() и найти/заменить ее вашей оберткой.

void myFoo(){ 
    return foo(); 
} 

Вместо вызова foo() вызова myFoo().

Надеюсь, это поможет вам.

+0

Цените его. Я знаю, что я прошу, это несколько недостижимо, но я все еще жду чуда. :) – Sheldon

1

Если у вас есть источник, вы можете обработать код с помощью GCC без изменения источника, добавив -finstrument-functions для сборки файлов, содержащих интересующие вас функции. Затем вам нужно будет написать функции __cyg_profile_func_enter/exit распечатайте трассировку. Пример из here:

#include <stdio.h> 
#include <time.h> 

static FILE *fp_trace; 

void 
__attribute__ ((constructor)) 
trace_begin (void) 
{ 
fp_trace = fopen("trace.out", "w"); 
} 

void 
__attribute__ ((destructor)) 
trace_end (void) 
{ 
if(fp_trace != NULL) { 
fclose(fp_trace); 
} 
} 

void 
__cyg_profile_func_enter (void *func, void *caller) 
{ 
if(fp_trace != NULL) { 
fprintf(fp_trace, "e %p %p %lu\n", func, caller, time(NULL)); 
} 
} 

void 
__cyg_profile_func_exit (void *func, void *caller) 
{ 
if(fp_trace != NULL) { 
fprintf(fp_trace, "x %p %p %lu\n", func, caller, time(NULL)); 
} 
} 

Другой путь, если у вас есть источник перекомпилировать библиотеку в качестве общей библиотеки. Оттуда можно выполнять вставки времени исполнения собственного .so/.dll с использованием любого количества систем отладки. (ltrace на unix, что-то в Windows [кто-то на окнах - пожалуйста, отредактируйте]).

Если у вас нет источника, я бы подумал, что ваш вариант 3 все равно должен работать. Люди, пишущие вирусы, делали это уже много лет. Возможно, вам придется провести ручную проверку (потому что инструкции x86 не имеют одинаковой длины), но трюк состоит в том, чтобы вытащить полную инструкцию и заменить ее прыжком в безопасное место. Сделайте то, что вам нужно сделать, верните регистры в такое же состояние, как если бы команда, которую вы удалили, запустила, а затем прыгайте сразу после введенной инструкции перехода.

+0

Большое спасибо за вашу помощь ~ Я просто разместил решение с помощью опций '\ Gh' и' \ GH'. Эти 2 варианта выполняют аналогичные функции, как опция «-finstrument-functions» GCC. :) – Sheldon

0

Компилятор VC предоставляет 2 возможности /Gh & /GH для подключения функций.

/Gh флаг вызывает вызов функции _penter в начале каждого метода или функции, а /GH флаг вызывает вызов функции _pexit в конце каждого метода или функции.

Итак, если я напишу код в _penter, чтобы узнать адрес функции вызывающего абонента, то я могу выборочно перехватить любую функцию, сравнив адрес функции.

Я сделал образец:

#include <stdio.h> 

void foo() 
{ 

} 

void bar() 
{ 

} 

void main() { 
    bar(); 
    foo(); 
    printf ("I'm main()!"); 
} 


void __declspec(naked) _cdecl _penter(void) 
{ 
    __asm { 
     push ebp;    // standard prolog 
     mov ebp, esp; 
     sub esp, __LOCAL_SIZE 
     pushad;     // save registers 
    } 

    unsigned int addr; 
    // _ReturnAddress always returns the address directly after the call, but that is not the start of the function! 
    // subtract 5 bytes as instruction for call _penter 
    // is 5 bytes long on 32-bit machines, e.g. E8 <00 00 00 00> 
    addr = (unsigned int)_ReturnAddress() - 5; 

    if (addr == foo) printf ("foo() is called.\n"); 
    if (addr == bar) printf ("bar() is called.\n"); 

    _asm { 
     popad;    // restore regs 
     mov esp, ebp;  // standard epilog 
     pop ebp; 
     ret; 
    } 
} 

Стройте с cl.exe source.c /Gh и запустить его:

bar() is called. foo() is called. I'm main()!

Это прекрасно!

Больше примеров о том, как использовать _penter и _pexit можно найти здесь A Simple Profiler и tracing with penter pexit и A Simple C++ Profiler on x64.

Я решил проблему, используя этот метод, и надеюсь, что это тоже поможет.

:)

Смежные вопросы