Russian Qt Forum
Май 06, 2024, 22:37 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Работа с "чужим" Qt приложением  (Прочитано 9126 раз)
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« : Июнь 27, 2011, 11:33 »

Нашел на просторах интернета решение, которое привожу ниже. Автор не известен. Решение не проверял.

Имеем: некое Qt приложение, собранное с динамическим подключением Qt библиотек и без исходников.
Задача: "подключиться" к этому приложению и работать с его объектами обычными Qt методами.
Решение: собрать проксирующую dll для любой Qt библиотеки, и вместо вызова любой родной Qt функции, вызвать свою, в которой уже выполнить инжектный код, который в итоге и выполнит всю работу.

А теперь полное описание с пояснениями.

Возьмём абстрактный пример: в неком приложении есть QLineEdit, в котором выводятся данные, которые хочется собирать. И собирать их хочется простым и понятным QLineEdit::text(), а не разбором скриншота экрана, или использованием WINAPI функций не имеющих ничего общего с Qt.
Теория:
Найти требуемый QLineEdit (указатель на этот объект в памяти) достаточно просто и вариантов для этого огромное количество, к примеру: перебираем QApplication::topLevelWidgets(), их детей, и находим в итоге требуемый нам QLineEdit.
Одна проблема: что бы получить именно виджеты нужного нам приложения, этот код надо выполнить в адресном пространстве этого приложения - как будто это прописано программистами Улыбающийся, однако исходников у нас нет, что бы вписать туда нужные нам строки.
Решением будет подменить библиотеку Qt, вписав туда нужный код - её приложение так или иначе загружает и считает это нормальным.
Но тут уже приходит другой минус - пересобирать огромную библиотеку (а бывает, что и не одну) ради пары строк кода (минимум, а в реальности придётся её пересобирать много раз) - это извращение.
Поэтому решилось всё так:

1. пишем свою собственную библиотеку, в которой создаём экспортируемую функцию, которая будет выполнять наш код:
Код
C++ (Qt)
//myInjectObject.h
 
#ifndef CPANEL_H
#define CPANEL_H
 
#include <QtCore/qglobal.h>
#include <QWidget>
#include <QEvent>
 
 
#if defined(QTHOOKDLL_LIBRARY)
#  define QTHOOKDLLSHARED_EXPORT Q_DECL_EXPORT
#else
#  define QTHOOKDLLSHARED_EXPORT Q_DECL_IMPORT
#endif
 
 
class QTHOOKDLLSHARED_EXPORT evFilter : public QObject
{
   Q_OBJECT
public:
   evFilter(QObject * parent = 0);
   void install();
protected:
   bool eventFilter( QObject* obj, QEvent *event );
};
 
 
class QTHOOKDLLSHARED_EXPORT CPanel : public QWidget
{
   Q_OBJECT
public:
   explicit CPanel(QWidget *parent = 0);
};
 
#endif // CPANEL_H
 
 

Код
C++ (Qt)
//myInjectObject.cpp
 
#include "cpanel.h"
#include <QApplication>
#include <QPainter>
 
 
evFilter::evFilter(QObject *parent): QObject(parent)
{
   ;
}
 
void evFilter::install()
{
   static bool injected = false;
   if ( injected ) return;
   injected = true;
 
   evFilter * evf = new evFilter(QApplication::instance());
   QApplication::instance()->installEventFilter(evf);
 
   CPanel * cPanel = new CPanel();
   connect(QApplication::instance(),SIGNAL(aboutToQuit()),cPanel,SLOT(deleteLater()));
   cPanel->show();
}
 
bool evFilter::eventFilter( QObject* obj, QEvent *event )
{
   if ( obj->inherits("QWidget") )
   {
       ;
   }
 
   if ( event->type() == QEvent::Paint ) {
       static bool block = false;
       if ( !block ) {
           block = true;
           QWidget* widget = static_cast<QWidget*>( obj );
           QApplication::sendEvent( widget, event );
 
           QPainter p( widget );
           QPen oldpen( p.pen() );
           int w=2;
           p.setPen( QPen( QBrush( QColor(255,0,0) ), w, Qt::SolidLine ) );
           p.drawRect( 0, 0, widget->width(), widget->height() );
           p.setPen( oldpen );
           block = false;
           return true; // We already processed the paint via the sendEvent
       }
   }
   return false;
}
 
CPanel::CPanel(QWidget *parent) :
   QWidget(parent)
{
}
 

получаем библиотеку (у меня она называется QtHook.dll), которая экпортирует два класса с их методами.

при вызове функции evFilter::install() для QApplication установится объект, который будет перехватывать все события внутри приложения (а это вообще все события, которые происходят у любого объекта - а значит мы получили полный контроль над приложением), при событии перерисовки (любого потомка QWidget, а значит и нужного нам QLineEdit) ему нарисуется красивая красная рамочка Улыбающийся просто что бы убедиться, что всё работает Улыбающийся, и создастся наш собственный виджет - объект класса CPanel - в нём мы уже можем аккуратно выводить всё что нам самим нужно. О статических переменных внутри функции evFilter::install() будет написано ниже.

В таблице экспорта библиотеки видна функция evFilter::install(). Правда, имя у неё не такое красивое (у меня это _ZN8evFilter7installEv) ну да это не важно.

2. качаем fasm.

3. берём чудо макрос (автор макроса - пользователь l_inc с форума http://wasm.ru)
Код
ASM
;FORWARDEDEXPORT.INC
 
macro dbDec@forwardedexport method*,num*
{
local tenPow,rest,number
number = num
tenPow = 1
while tenPow <= number
tenPow = tenPow*10
end while
if tenPow = 1
method '0'
else
rest = number
while tenPow > 1
tenPow = tenPow/10
method rest/tenPow + '0'
rest = rest mod tenPow
end while
end if
}
 
macro exportForwarded dllname*, forwardedDllPath*, [label,name_ord]
{
forward
;some simple arguments validity checks
if ~ label eq
if name_ord eq
display 'No function name or ordinal for label ',"'",`label,"'",' specified',13,10
err
end if
else
if ~ name_ord eq
if name_ord eqtype 0
display 'No label for ordinal ', `name_ord, ' specified',13,10
else
display 'No label for function ',"'",name_ord,"'",' specified',13,10
end if
err
end if
end if
common
local PEHeaderOffset, NumberOfSections, SizeOfOptionalHeader, ExportDirectoryRVA,\
SectionTableOffset, SectionVSize, SectionRVA, SectionOffset,\
ExportDirectoryOffset, OrdinalBase, NumberOfFunctions, NumberOfNames,\
FunctionsOffset, NamesOffset, OrdinalsOffset,\
dllnameptr, addresses, names, ordinals,\
NameStrings, ForwardNameStrings, ForwardOrdinalStrings,\
ForwardDllNameStart, ForwardDllNameLen, NameOffset, FunctionIndex, found, buf, c1, c2
 
virtual
file forwardedDllPath:3Ch,4 ;3Ch - e_lfanew field offset
load PEHeaderOffset dword from $-4
file forwardedDllPath:PEHeaderOffset+6h,2 ;6h - NumberOfSections offset
load NumberOfSections word from $-2
file forwardedDllPath:PEHeaderOffset+14h,2 ;14h - SizeOfOptionalHeader offset
load SizeOfOptionalHeader word from $-2
file forwardedDllPath:PEHeaderOffset+78h,4 ;78h - IMAGE_EXPORT_DIRECTORY offset
load ExportDirectoryRVA dword from $-4
end virtual
 
SectionTableOffset = PEHeaderOffset + 18h + SizeOfOptionalHeader ;18h - Size of FileHeader + size of Signature
repeat NumberOfSections
virtual
file forwardedDllPath:SectionTableOffset+(%-1)*28h,28h ;28h - size of IMAGE_SECTION_HEADER
load SectionVSize dword from $$+8h ;8h - VirtualSize offset
load SectionRVA dword from $$+0Ch ;0Ch - VirtualAddress offset
load SectionOffset dword from $$+14h ;14h - PointerToRawData offset
end virtual
if SectionRVA <= ExportDirectoryRVA & ExportDirectoryRVA < SectionRVA + SectionVSize
ExportDirectoryOffset = SectionOffset + ExportDirectoryRVA - SectionRVA
break
end if
end repeat
if ~ defined ExportDirectoryOffset
display 'Error: export directory not found',13,10
err
end if
virtual
file forwardedDllPath:ExportDirectoryOffset,28h ;28h - size of IMAGE_EXPORT_DIRECTORY
load OrdinalBase dword from $$+10h ;10h - Base offset
load NumberOfFunctions dword from $$+14h ;14h - NumberOfFunctions offset
load NumberOfNames dword from $$+18h ;18h - NumberOfNames offset
;load FunctionsOffset dword from $$+1Ch ;1Ch - AddressOfFunctions offset
; FunctionsOffset = SectionOffset + FunctionsOffset - SectionRVA
load NamesOffset dword from $$+20h ;20h - AddressOfNames offset
NamesOffset = SectionOffset + NamesOffset - SectionRVA
load OrdinalsOffset dword from $$+24h ;24h - AddressOfNameOrdinals offset
OrdinalsOffset = SectionOffset + OrdinalsOffset - SectionRVA
end virtual
 
virtual
db forwardedDllPath
ForwardDllNameLen = 0
buf = 0
while $ - $$ >= %
load buf byte from $-%
if buf = '.' | buf = '/' | buf = '\'
break
end if
ForwardDllNameLen = ForwardDllNameLen + 1
end while
ForwardDllNameStart = ForwardDllNameLen
if buf = '.'
while $ - $$ >= ForwardDllNameLen+%
load buf byte from $-ForwardDllNameLen-%
if buf = '/' | buf = '\'
break
end if
ForwardDllNameStart = ForwardDllNameStart + 1
end while
ForwardDllNameLen = ForwardDllNameStart-ForwardDllNameLen
else
ForwardDllNameLen = ForwardDllNameLen + 1
end if
ForwardDllNameStart = ($-$$)-ForwardDllNameStart
end virtual
 
dd 0,0,0,RVA dllnameptr,OrdinalBase
dd NumberOfFunctions,NumberOfNames,RVA functions,RVA names,RVA ordinals
 
functions: times NumberOfFunctions dd 0
forward local unforwarded
;store own stub-exports pointed by ordinals
if name_ord eqtype 0
if name_ord < OrdinalBase | OrdinalBase + NumberOfFunctions <= name_ord
display 'Error: ordinal ', `name_ord, ' does not belong to the export of "', forwardedDllPath, '"',13,10
err
else
load buf dword from functions+(name_ord - OrdinalBase)*4
if ~ buf
store dword RVA label at functions+(name_ord - OrdinalBase)*4
else
display 'Error: attempt to assign the label ',"'",`label,"'",' to the already used ordinal ', `name_ord,13,10
err
end if
unforwarded = 1
end if
end if
common
names: times NumberOfNames dd 0
 
ordinals:
repeat NumberOfNames/4
virtual
file forwardedDllPath:OrdinalsOffset+(%-1)*8,8
load buf qword from $$
end virtual
dq buf
end repeat
repeat NumberOfNames mod 4
virtual
file forwardedDllPath:OrdinalsOffset+(NumberOfNames/4*4+%-1)*2,2
load buf word from $$
end virtual
dw buf
end repeat
 
dllnameptr db dllname,0
 
NameStrings:
repeat NumberOfNames
store dword RVA $ at names+(%-1)*4
 
;read function name offset from file
virtual
file forwardedDllPath:NamesOffset+(%-1)*4,4
load NameOffset dword from $$
NameOffset = SectionOffset + NameOffset - SectionRVA
end virtual
 
;copy the function name here
while 1
virtual
file forwardedDllPath:NameOffset+(%-1)*8,8
load buf qword from $$
end virtual
rept 8
\{
db (buf and 0xFF)
if ~(buf and 0xFF)
break
end if
buf = buf shr 8
\}
end while
end repeat
 
ForwardNameStrings:
repeat NumberOfNames
;get function index
load FunctionIndex word from ordinals+(%-1)*2
;get function name offset
load NameOffset dword from names+(%-1)*4
NameOffset = NameOffset + ($ - RVA $)
;get currently saved pointer in order to check
;whether the pointer is not going to be overridden
load buf dword from functions+FunctionIndex*4
;check whether the function should be forwarded
found = 0
forward
if name_ord eqtype ''
while 1
load c1 byte from NameOffset+(%-1)
virtual
db name_ord,0
load c2 byte from $$+(%-1)
end virtual
if c1 <> c2
break
end if
if c1 = 0
if buf
display 'Error: attempt to assign the label ',"'",`label,"'",\
' to the function ',"'",name_ord,"'",' already labeled by ordinal '
dbDec@forwardedexport display,FunctionIndex+OrdinalBase
display 13,10
err
else if found
display 'Error: attempt to assign the label ',"'",`label,"'",\
' to the function ',"'",name_ord,"'",' already labeled by name',13,10
err
else
found = 1
store dword RVA label at functions+FunctionIndex*4
unforwarded = 1
end if
break
end if
end while
end if
common
if  ~ buf & ~ found
store dword RVA $ at functions+FunctionIndex*4
;store the name of the destination dll
repeat ForwardDllNameLen
virtual
db forwardedDllPath,'.'
load buf byte from $$+ForwardDllNameStart+(%-1)
if 'a' <= buf & buf <= 'z'
buf = buf - 'a' + 'A'
end if
end virtual
db buf
end repeat
;copy the name of forwarded function
while 1
load buf byte from NameOffset+(%-1)
db buf
if ~ buf
break
end if
end while
end if
end repeat
forward
if ~ defined unforwarded & ~ label eq
display 'Error: function ',"'",name_ord,"'",' does not belong to the export of ','"',forwardedDllPath,'"',13,10
err
end if
common
ForwardOrdinalStrings:
repeat NumberOfFunctions
load buf dword from functions+(%-1)*4
if ~ buf
store dword RVA $ at functions+(%-1)*4
repeat ForwardDllNameLen
virtual
db forwardedDllPath,'.'
load buf byte from $$+ForwardDllNameStart+(%-1)
if 'a' <= buf & buf <= 'z'
buf = buf - 'a' + 'A'
end if
end virtual
db buf
end repeat
db '#'
dbDec@forwardedexport db,%-1+OrdinalBase
db 0
end if
end repeat
}
 

4. смотрим приложение, в которое хочется вторгнуться, на предмет используемых Qt функций.
Мой "подопытный" использовал метод QMainWindow::event(QEvent*);, который в списке импорта (и экспорта библиотеки QtGui4) называется как _ZN11QMainWindow5eventEP6QEvent. Он гарантированно используется при выполнении приложения, поэтому в него и было решено заинжектиться. Однако этот метод будет вызываться не однократно - ведь событий у QMainWindow - огромное количество и происходят они постоянно, а заинжектиться достаточно один раз - именно для этого служит код
Код
C++ (Qt)
void evFilter::install()
{
   static bool injected = false;
   if ( injected ) return;
   injected = true;
   .......
 

5. В данном случае проксируется QtGui4.dll, так что переименовываем оригинальную  - я её переименовал в QtGui4_origianl.dll

6. Берём код нашей "псевдо" Qt библиотеки. В данном случае я проксировал QtGui4.dll.

Код
ASM
;QtGui4.asm
 
format PE GUI 4.0 DLL
entry DllEntryPoint
 
include 'win32a.inc'
include 'forwardedexport.inc'
 
section '.text' code readable executable
 
proc DllEntryPoint hinstDLL,fdwReason,lpvReserved
   mov    eax,TRUE
   ret
endp
 
invoke ExitProcess,0
 
hookEd:
   push eax
   push ecx
   push edx
invoke hook
   pop edx
   pop ecx
   pop eax
jmp [hookedFunc]
 
section '.idata' import data readable writeable
   library kernel32,'kernel32.dll',\
   qtHook,'QtHook.dll',\
   qtGui4origianl,'QtGui4_original.dll'
 
   import kernel32,\
ExitProcess,'ExitProcess'
 
   import qtGui4origianl,\
hookedFunc,'_ZN11QMainWindow5eventEP6QEvent'
 
   import qtHook,\
hook,'_ZN8evFilter7installEv'
 
section '.edata' export data readable
 
   exportForwarded 'QtGui4.dll','QtGui4_original.dll',\
hookEd,'_ZN11QMainWindow5eventEP6QEvent'
 
section '.reloc' fixups data discardable
 
что этот код делает?
он компилируется в библиотеку, которая содержит точно такой же список экспорта, как и QtGui4_original.dll, но на функцию _ZN11QMainWindow5eventEP6QEvent она обращается к внутренней функции hookEd, которая сначала вызывает функцию, описанную как hook (строка hook,'_ZN8evFilter7installEv'), а потом уже вызывает родную Qt функцию. Если представить себе код на c++ - то получится что наша функция evFilter::install() вызывается первой строкой внутри QMainWindow::event(QEvent*);. Если немного изменить ассемблерный код, то можно даже воспользоваться передаваемыми переменными Улыбающийся

всё! компилируем, кладём три библиотеки (QtHook.dll, QtGui4_original.dll и скомпилированную QtGui4.dll) вместо родной QtGui4.dll, запускаем "подопытного" и радуемся красным рамочкам и нашему виджету класса CPanel Улыбающийся
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #1 : Июнь 27, 2011, 15:02 »

Ну что сказать, реализация грамотная и довольно очевидная (конечно после того как подробно объяснили Улыбающийся). Насчет "полного контроля" громко сказано, это хук/шпион, но большего без исходников и не получить.

Вызывает сожаление что столько труда вложено чтобы извлечь что-то из чужого приложения - это бы на добрые (да просто "нормальные") цели.
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #2 : Июнь 27, 2011, 15:09 »

Благодаря этому решению, можно сделать тестер Qt прог.
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
asvil
Гость
« Ответ #3 : Июнь 27, 2011, 17:28 »

а потом тестер тестера прог
Записан
Denjs
Гость
« Ответ #4 : Июнь 27, 2011, 17:52 »

Благодаря этому решению, можно сделать тестер Qt прог.
Ога. я именно так - почти -  и поступаю. Т.е. конечно более цивилизованно...
Заставляю тестируемое приложение Подгрузить мою библиотеку и тупо вызывть init() (кажется) без параметров.

 а там - QtApplication::instance() - и побежали по всем виджеты и их дочерние, хук на главное окно и прочее. В общем если про тесты - смотреть http://www.prog.org.ru/topic_14719_0.html
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.226 секунд. Запросов: 23.