[C++]Обращение к методу через vtable.

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by m1nt@ll, 13 Apr 2012.

  1. m1nt@ll

    m1nt@ll New Member

    Joined:
    9 Feb 2012
    Messages:
    19
    Likes Received:
    1
    Reputations:
    0
    В данный момент, пишу плагин на C++ для сервера Counter-Strike:Source. К сожалению, многие классы устарели, так как SDK уже года 2 как не обновлялся, однако, можно получить интерфейс класса, дизассемблировав линукс-бинарники сервера. С этим и связана проблема...


    Первый вопрос.
    И так, по порядку... Сначала я пытался использовать востановленый интерфейс класса CBasePlayer. Для этого я использовал IDA + мое ПО для построения класса. Получаю я примерно следующее:

    PS. Класс CBasePlayer наследует несколько классов, если что...
    Code:
    abstract_class IBasePlayer_G
    {
    public:
    	~IBasePlayer_G(); // 0
    	virtual void /*Unknown value*/ /*CBaseEntity*/ SetRefEHandle(CBaseHandle  const&); // 1
    	virtual void /*Unknown value*/ /*CBaseEntity*/ GetRefEHandle(void)const; // 2
    	virtual void /*Unknown value*/ /*CBaseEntity*/ GetCollideable(void); // 3
    	virtual void /*Unknown value*/ /*CBaseEntity*/ GetNetworkable(void); // 4
    	virtual void /*Unknown value*/ /*CBaseEntity*/ GetBaseEntity(void); // 5
    	virtual void /*Unknown value*/ /*CBaseEntity*/ GetModelIndex(void)const; // 6
    	virtual void /*Unknown value*/ /*CBaseEntity*/ GetModelName(void)const; // 7
    	virtual void /*Unknown value*/ /*CBaseEntity*/ SetModelIndex(int); // 8
    	virtual void /*Unknown value*/ /*CBasePlayer*/ GetServerClass(void); // 9
    
            ...
    	virtual void /*Unknown value*/ /*CBasePlayer*/ CommitSuicide(bool,bool); // 432
    	virtual void /*Unknown value*/ /*CBasePlayer*/ CommitSuicide(Vector  const&,bool,bool); // 433
    	...
    };
    
    Я пытаюсь обратиться к методу CommitSuicide следующим образом:
    Code:
    reinterpret_cast< ICSPlayer_G* >( pBase )->CommitSuicide( Vector( 0, 0, 0 ), true, true ); 
    Однако, этот код вызывает ошибку чтения памяти, и правда, во время отладки я вижу примерно следующее:
    Code:
     mov eax, [ecx]
     mov eax, [eax]
     call [eax + 421 * 4 ]
    
    Не понятно, почему вызывается функция с каким-то левым индексом, если я обращаюсь к CommitSuicide, у которой индекс равен 433?

    Второй вопрос.

    Я нашел решение своей проблемы, но мне не совсем ясна реализация этого решения. Я нашел на сайте, посвященном разработке плагинов для движка Source примерно следующий код:

    Code:
    class EmptyClass {};
    
    static void CommitSuicide( CBaseEntity* pEntity )
    		{
    			void **this_ptr = *(void ***)&pEntity;
    			void **vtable = *(void ***)pEntity;
    			void *func = vtable[433]; 
    
    			union {	CBaseEntity *(EmptyClass::*mfpnew)( void );
    			void *addr;	} u; 	u.addr = func;
    
    			(reinterpret_cast<EmptyClass*>(this_ptr)->*u.mfpnew)();
    		}
    
    Он работает корректно, и убивает игрока, как положено. Однако, как видно из сигнатуры вызова, в него не передаются параметры из сигнатуры метода, который я получил, дизассемблировав бинарники. Как вы помните, он выглядит следующим образом: CommitSuicide(Vector const&,bool,bool). Как такое возможно? Функция работает корректно, но в нее не передаются необходимые параметры!

    PS.
    При отладке вышеупомянутой реализации, я получаю вызов по корректному индексу (433):

    Code:
     mov eax, [ecx]
     mov eax, [eax]
     call [eax + 433 * 4 ]
    
    Извиняюсь если сделал ошибки в ассемблерном коде, я лишь хотел показать индексы по которым вызываются виртуальные функции.
     
    #1 m1nt@ll, 13 Apr 2012
    Last edited: 13 Apr 2012
  2. m1nt@ll

    m1nt@ll New Member

    Joined:
    9 Feb 2012
    Messages:
    19
    Likes Received:
    1
    Reputations:
    0
    Up.
    __________________