Нашел на просторах интернета решение, которое привожу ниже. Автор не известен. Решение не проверял.
Имеем: некое 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 (http://flatassembler.net/).
3. берём чудо макрос (автор макроса - пользователь
l_inc с форума http://wasm.ru (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 :)
Ну что сказать, реализация грамотная и довольно очевидная (конечно после того как подробно объяснили :)). Насчет "полного контроля" громко сказано, это хук/шпион, но большего без исходников и не получить.
Вызывает сожаление что столько труда вложено чтобы извлечь что-то из чужого приложения - это бы на добрые (да просто "нормальные") цели.
Благодаря этому решению, можно сделать тестер Qt прог.