Wiemy już, jak pisać do notatnika bez użycia notatnika. To może teraz czas na coś zgoła innego, jak przypuśćmy...uruchomienie notatnika z notatnika?
[tut]Igraszki z notatnikiem #2, czyli jak uruchomić notatnik z notatnika?
Wstęp
W dzisiejszym artykule spróbujemy jakoś kreatywnie wykorzystać to, czego dowiedzieliśmy się w poprzednim artykule, tj. gdzie jest editBox Notatnika, jak czytać z niego informacje i jak do niego pisać. Pytanie tylko, jak to możemy wykorzystać żeby uruchomić notatnik...
...Każdy, dosłownie każdy miał styczność z czymś tak fajnym jak interpeter poleceń, vel cmd.exe. Bardziej dociekliwe osoby wiedzą że jest coś takiego jak przełącznik /C...
Rys. 1 - Przełącznik /C. Ciekawy, prawda?
...który moglibyśmy wykorzystać wraz z tzw.przekierowaniem strumieni/potoku do pliku...
Rys. 2 - Przkierowanie wyjścia danego programu do pliku. Bardzo przydatna rzecz.
Jakiś niecny pomysł zaczyna się klarować, prawda? Pomyślmy, timer, który w krótkich odstępach czasu(powiedzmy, 100ms) za pomocą GetAsyncKeyState sprawdzałby czy użytkownik nacisnął jakiś z góry ustalony klawisz(np.klawisz tab). Jak tak, odpalałby funkcję z DLL'ki, która za pomocą GetWindowText pobierałaby zawartość editBoxa, wrzucała jego zawartość do parametru lpCommandLine funkcji CreateProcess, i przekierowałaby jego zawartość do wcześniej utworzonego pliku tymczasowego(a co, konspiracja na sto procent), a później za pomocą odpowienich funkcji z WinApi ładowałaby do pamięci, a stamtąd za pomocą SetWindowText do editBoxa...no cóż, cel i kroki mamy, teraz pora nałożyć buty i...
W drogę!
Więc jak nie wspomniałem wcześniej, to wspomnę teraz - cały nasz niecny plan będzie opierać się na technice DLL Injection. Nie będę wdawał się w opisy, bo z pewnością powstało na ten temat dużo ciekawych poradników, znacznie lepszych niż mój. Wspomnę jedynie, że nie będę trudził się z pisaniem własnego szkieletu - wykorzystam sobie gotowy projekt z Dev-C++(tyle że dla języka C - tworzenie własnych obiektów mnie przeraża), a za injector posłuży mi poczciwy Cheat Engine w wersji 5.6.1(jakoś nie pałam szczególną miłością do LUA). Okej, na rozgrzewkę napiszmy sobie jakąś prostą dllkę...
Rys. 3 - przykładowa dllka.
Śpieszę z wyjaśnieniami cóż to robi - w naszej ulubionej case(eee....wiecie jak się nazywa ) zarzucam funkcję SetTimer, gdzie jako uchwyt okna stosuję FindWindow z podaną wcześniej klasą "Notepad", a jako ostatni parametr podaję adres callbacku który SetTimer przyjmuje, tj.TimerProc.A sam timer...no cóż, jak widać tylko śmieci. '
Szast-prast, piszemy dalej:
Rys. 22 - bardziej zaawansowany szkic dllki, który właściwie robi wszystko co chcemy. Oprócz tego, że nie wywołuje cmd. >.>
Pierwsza linijka robi tzw.wskaźnik - z uwagi na to, że działamy już w obrębie notatnika, to możemy się pokusić o coś takiego.
Dlaczego sprawdzam czy GetAsyncKeyState jest mniejsze od zera? Ponieważ ta funkcja "zapala" pierwszy bajt(który świadczy o tym, czy liczba jest dodatnia, czy ujemna - pozdrawiam wszystkich z profilu mat-fiz-inf!). Reszta rzeczy jest raczej zrozumiała, więc przejdę do finalnej części, czyli o finalnym proof-of-concept:
/* Replace "dll.h" with the name of your header */
#include "dll.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
HWND *editBox = (HWND*)0x01009838; //ach, te pisanie dllek ^_^
char hTempName[128];
void cdUp()
{
char *temp = malloc(128);
int licznik = 0, i;
GetCurrentDirectory(128, temp);
for(i = 0; i < strlen(temp); i++)
if(temp[i]=='\\')
licznik = i;
temp[licznik] = 0;
SetCurrentDirectory(temp);
//zeby nie bylo, to jeszcze sobie wyczyscimy editboxa
ZeroMemory(temp, 128);
SetWindowText(*editBox, temp); //dzialamy na wskazniku, wiec powinnismy go sobie "luskac",jak chcemy z niego skorzystac
free(temp); //na wszelki wypadek
}
void cdDown(char *katalog)
{
char *temp = malloc(128);
GetCurrentDirectory(128, temp);
strcat(temp,"\\");
strcat(temp, katalog);
SetCurrentDirectory(temp);
ZeroMemory(temp, 128);
SetWindowText(*editBox, temp);
free(temp);
}
void executeCommand(char *command)
{
char *output = (char*)malloc(2048); //a teraz dla odmiany czyste c...
PROCESS_INFORMATION pi;
STARTUPINFO si;
DWORD dwRead;
ZeroMemory(output, 2048);
command[strlen(command)-1] = 0; //tutaj usuwamy znak tabulatora, ktory zwykle jest na koncu
if(command[0] == 'c' && command[1] == 'd')
if(command[3] == '.' && command[4] == '.' && command[2] == ' ')
{cdUp();return;}
else
{cdDown((command+3));return;} //zakrawa na memory leak, ale co tam, to raptem 3 bajty
strcat(output, "/C \""); //strcat(a, ; - do ciagu "a" dolacza ciag "b", na koniec, oczywiscie
strcat(output, command);
strcat(output, " >");
strcat(output, hTempName);
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO); //wymagane przez system
si.dwFlags = STARTF_USESHOWWINDOW; //ta flaga mowi Windowsowi zeby patrzyl na ponizsza zmienna
si.wShowWindow = SW_HIDE; //A tu mowimy zeby kazde wywolanie konsoli mialo flage SW_HIDE, czyli zeby nam nie migalo
CreateProcess( "C:\\windows\\system32\\cmd.exe",
output,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
π);
ZeroMemory(output, 2048); //zeby bylo oszczedniej to korzystam z jednej zmiennej - a co!
//Flagi ponizej dobrze tlumaczy MSDN
HANDLE hTempFile = CreateFile(hTempName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
ReadFile(hTempFile, output, 2048, &dwRead, 0);
CloseHandle(hTempFile);
SetWindowText(*editBox, output);
free(output);
}
void CALLBACK SprawdzCzyUzytkownikWcisnalKlawisz(HWND hWnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
char *input = (char*)malloc(128);
ZeroMemory(input,128);
if(GetAsyncKeyState(VK_TAB)<0) //jezeli jest mniejsze od zera, znaczy ze wcisniete
{
GetWindowText(*editBox, input, 128);
executeCommand(input);
}
free(input);
}
void getTemp()
{
char *currentdir = (char*)malloc(128);
ZeroMemory(currentdir, 128);
ZeroMemory(hTempName, 128);
GetTempPath(128, currentdir); //zwraca folder z plikami tymczasowymi
GetTempFileName(currentdir, 0, 0, hTempName); //zwraca nazwę
}
BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,
DWORD reason /* Reason this function is being called. */ ,
LPVOID reserved /* Not used. */ )
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
getTemp();
SetTimer(FindWindow("Notepad", 0), 0, 100, SprawdzCzyUzytkownikWcisnalKlawisz);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
/* Returns TRUE on success, FALSE on failure */
return TRUE;
}
To teraz pora to potłumaczyć...
FAQ
Q:Do czego służą funkcje cdUp i cdDown?
A:Zacznę od początku - żeby nasza "nowoczesna konsola" mogła poruszać się pomiędzy katalogami, to trzeba było wymyślić jakiś sposób, dzięki któremu mogłaby to zrobić - odpowiedzią jest właśnie CreateProcess, który każdy utworzony proces wywołuje w tzw. current working directory, czyli w katalogu którym teoretycznie sam się odpalił, i który możemy dowolnie zmieniać za pomocą funkcji SetCurrentDirectory i GetCurrentDirectory. Funkcja cdUp szuka pozycji ostatniego slasha - "\" w tym co zwróci nam GetCurrentDirectory i w jego pozycję wstawia nam 0, vel null. Co to daje? C jest tak fajnym językiem, że każdy ciąg znaków jest zakończony tym właśnie zerem(możemy się spotkać chociażby na msdnie z określeniami "null terminated string") i resztę po zerze po prostu nie bierze pod uwagę. cdDown porusza się "w dół", dodając jednego slasha i nazwę katalogu do którego chcemy się przenieść.
Q:Dlaczego za każdym razem tworzę sobie uchwyt do pliku tymczasowego?
A:Bo inaczej nie działa. Tzn. nie chciało, u mnie. :<
I tak to mniej-więcej działa. Chociaż działa trochę dziwnie, co można zaobserwować na filmiku(oj tam, wpisywanie dwa razy każdej komendy to nie problem!):
No i cóż, to by było na tyle.
Komentarze(i odpowiedź, jak rozwiązać problem z podwójnym wpisywaniem komend) mile widzane, jak zwykle. : D
Pozdrawiam,
JP na 100%