
Управление другим приложением из своей программы
Под управлением сторонним приложением я подразумеваю некие действия своей программы, эмулирующие действия живого человека. Например, нажатия на кнопки в диалоговых окнах. Попробуем сделать это на примере лоадера для популярного файлового менеджера Total Commander. Кто пользуется им, тот знает, что единственное различие между полной и незарегистрированной версией в том, что триалка при запуске показывает наг-скрин с предложением купить программу или нажать для продолжения одну из трех кнопок с цифрами. Конечно, кнопку можно нажать самостоятельно, но можно доверить это лоадеру.
Наг-скрин Total Commander
Чтобы работать с содержимым окна, сперва надо узнать его хэндл. Проще всего воспользоваться функцией FindWindow, указав в качестве параметра наименование класса наг-скрина. Его можно посмотреть при помощи моей программы WinDowzer или любой другой аналогичной программы. Противное окно имеет название класса TNASTYNAGSCREEN, первый шаг сделан. Теперь нам надо узнать хэндл нужной кнопки, для этого нужно изучить все связи родительских и дочерних объектов окна.
Иерархический список хэндлов всех элементов нужного окна можно получить при помощи последовательного вызова функции FindWindowEx или же рекурсивного вызова функции EnumChildWindows. Вот что у меня получилось:
TNASTYNAGSCREEN - Total Commander
TNotebook
TPage - NagPage
TPanel
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TButton - &Registration info
TButton - Program &information
TPanel
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TButton - &Registration info
TButton - Program &information
TPage - StartupPage
TPanel
TPanel
TPage - UserPage
TButton - &Update Information
TButton - &Update Information
TPage - NagPage
TPanel
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TButton - &Registration info
TButton - Program &information
TPanel
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TPanel - 3
TButton - &3
TButton - &2
TButton - &1
TButton - &Registration info
TButton - Program &information
TPage - StartupPage
TPanel
TPanel
TPage - UserPage
TButton - &Update Information
TButton - &Update Information
В списке для удобства я записал название класса и текст дочерних окон. Казалось бы, в момент перебора надо просто "нажать" нужную кнопку. Но здесь не все так просто, так как каждый раз выбирается случайный номер кнопки, и если нажать неправильную, то Total Commander просто завершит работу. Значит нам еще надо узнать правильный номер кнопки, прежде чем "нажать" на нее. Обратите внимание, что номер кнопки повторяется в форме несколько раз, и длина строки (то есть цифры) - всегда один символ. Облегчим себе задачу и рекурсивно переберем все дочерние элементы наг-скрина, а строку длиной один символ будем считать правильным номером.
Code (Assembler) : Убрать нумерацию
- ; Секция данных
- button db '&',0,0
- buff rb 3
- ; Секция кода
- ...
- invoke EnumChildWindows,[nag],FindNastyNumber,NULL
- ...
- proc FindNastyNumber hwnd:dword,lParam:dword
- mov [buff],0
- invoke GetWindowText,[hwnd],buff,100
- invoke lstrlen,buff
- cmp eax,1
- jne @f
- invoke lstrcpy,button+1,buff
- @@:
- invoke EnumChildWindows,[hwnd],FindNastyNumber,NULL
- mov eax,1
- ret
- endp
Лирическое отступление. Наиболее правильная эмуляция клика на каком-либо элементе окна выглядит следующим образом:
Code (Assembler) : Убрать нумерацию
- invoke SendMessage,[hwnd],WM_SETFOCUS,NULL,NULL
- invoke SendMessage,[hwnd],WM_LBUTTONDOWN,MK_LBUTTON,NULL
- invoke SendMessage,[hwnd],WM_LBUTTONUP,MK_LBUTTON,NULL
Code (Assembler) : Убрать нумерацию
- ...
- invoke EnumChildWindows,[nag],ClickNastyButton,NULL
- ...
- ; Рекурсивная функция перебора всех дочерних элементов
- proc ClickNastyButton hwnd:dword,lParam:dword
- mov [buff],0
- ; Прочитать текст дочернего элемента
- invoke GetWindowText,[hwnd],buff,3
- ; Это текст искомой кнопки?
- invoke lstrcmp,buff,button
- or eax,eax
- jnz @f
- ; Эмулировать нажатие на кнопку
- invoke SendMessage,[hwnd],WM_SETFOCUS,NULL,NULL
- invoke SendMessage,[hwnd],WM_LBUTTONDOWN,MK_LBUTTON,NULL
- invoke SendMessage,[hwnd],WM_LBUTTONUP,MK_LBUTTON,NULL
- @@:
- invoke EnumChildWindows,[hwnd],ClickNastyButton,NULL
- mov eax,1
- ret
- endp
Просмотров: 10606 | Комментариев: 7

Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье

SendMessageA
(09.01.2017 в 22:30):
Если хотите еще и запускать лоадером, то добавьте Sleep'ы, чтобы наг-скрин успел сформировать элементы своего окна:
totCom db 'D:\TOTALCMD\TOTALCMD64.EXE',0
sinfo STARTUPINFO
pinfo PROCESS_INFORMATION
section '.code' code readable executable
start:
invoke CreateProcessA,0,totCom,0,0,0,0,0,0,sinfo,pinfo
@@:
invoke Sleep,20
invoke FindWindow,szNag,NULL
or eax,eax
jz @b
mov [nag],eax
invoke Sleep,200
totCom db 'D:\TOTALCMD\TOTALCMD64.EXE',0
sinfo STARTUPINFO
pinfo PROCESS_INFORMATION
section '.code' code readable executable
start:
invoke CreateProcessA,0,totCom,0,0,0,0,0,0,sinfo,pinfo
@@:
invoke Sleep,20
invoke FindWindow,szNag,NULL
or eax,eax
jz @b
mov [nag],eax
invoke Sleep,200

Ret
(28.12.2012 в 18:10):
а я вспомнил про AutoHotkey)
www.autohotkey.com
www.autohotkey.com

vox13
(21.02.2012 в 21:37):
Согласен с автором: одно дело скрипт, а другое самому на асме замутить. Тем более можно применить для разных программ, не только для TC.

ManHunter
(13.02.2012 в 10:46):
Ну то s0m, а тут Ассемблер, все-таки разные вещи. Простой кликер\лоадер - да, можно на и на s0m нарисовать, а когда требуется управлять из своей программы какой-нибудь другой, то придется применять уже такие методы. К тому же s0m очень не любят многие антивири.

semenov
(13.02.2012 в 09:53):
Тоже сразу вспомнил про Sign 0f Misery http://s0m.narod.ru/

RedElf
(13.02.2012 в 01:26):
можно воспользоваться готовым решением типа SignOfMisery

Добавить комментарий
Заполните форму для добавления комментария

...
invoke CreateProcess,curdir,buffer,0,0,FALSE,0,0,0,sinfo,pinfo
test eax,eax
je procfail
loc_nag:
invoke WaitForInputIdle,[pinfo.hProcess],-1
xor esi,esi
mov esi,6
@@:
invoke Sleep,500
invoke FindWindow,sznag,0
test eax,eax
jne @F
dec esi
jne @B
jmp exit ; выход, если наг не найден (если случайно запущен крякнутый сабж или с ключом)
@@:
mov ebx,eax
...
Лучше, имхо, делать через FindWindowEx - в паре похожих лоадеров для других прог прекрасно работает такой цикл:
invoke ShellExecute,0,0,rdate,param,0,10
mov esi,1000000
@@:
invoke FindWindow,nag,0
or eax,eax
jne @F
dec esi
jne @B
jmp loc_exit
@@:
mov [hNAG],eax
@@:
invoke ShowWindow,[hNAG],SW_HIDE
or eax,eax
je @B
@@:
invoke FindWindowEx,[hNAG],0,tedit,0
or eax,eax
je @B
invoke GetWindow,eax,GW_HWNDLAST
mov [hID],eax
...
Т. е. достаточно счётчика, без Sleep'ов, но с EnumChildWindows так почему-то не хочет работать. Попробовал здесь сделать с FindWindowEx - сходу не получилось, глубже разбираться пока что лень. :)